;
; vmtime.asm
;

include vmbdoor.inc

; enable x486 instructions, but use 16 bit segment
.486
option segment:use16

; code segment
codes	segment para use16 public 'CODE'
codes	ends

; data segment
datas	segment byte use16 public 'DATA'
BDOOR_MAGIC	dword  	VMWARE_MAGIC
BDOOR_PORT  	word	VMWARE_PORT
DAY_PER_MONTH	byte	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
DAY_PER_MONTH_L	byte	31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
MSG_VMNG	byte	'Not running inside VMware.$'
MSG_TIMENG	byte	'Time syncronization failed.$'
MSG_CURRENT	byte	'20  /  /     :  :  $'
END_OF_LINE	byte	CR, LF, '$'
datas	ends

main	group codes,datas

codes	segment
; for tiny memory model (.com), all seg is the same
assume	cs:main,ds:main,es:main,ss:main
org	100h
entry:
check_vm	proc near
	; Get VMware version
        mov 	eax, [BDOOR_MAGIC]
        mov 	dx, [BDOOR_PORT]
	xor 	ecx,ecx
	mov 	cl, VMCMD_GET_VERSION
        in 	eax, dx

	; Check Magic number
	cmp	ebx,[BDOOR_MAGIC]
	jne	not_in_vm

	; Get host's system time
	mov	eax, [BDOOR_MAGIC]
	xor	ecx, ecx
	mov	cl, VMCMD_GET_TIME
	mov	dx, [BDOOR_PORT]
	in	eax, dx

	; gmt value OK?
	cmp	eax, eax
	js	invalid_time
check_vm	endp

;
; convert timevalue
;
conv_time	proc near
	; Convert gmt to localtime
	mov	ebx, eax		; ebx = gmt timevalue
	mov	eax, edx		; eax = offset from gmt (minutes)
	mov	ecx, 60			; ecx = secs per minute
	imul	ecx			; eax = offset from gmt (secs)
	sub	ebx, eax		; ebx = local timevalue

	; calculate year
	mov	ax, 1970

next_year:
	test	ax, 3
	jz	leap_year

	; normal year
	mov	ecx, 365 * 24 * 60 * 60	; secs per year
	mov	bp, offset DAY_PER_MONTH
	jmp	add_year

leap_year:
	mov	ecx, 366 * 24 * 60 * 60	; secs per year
	mov	bp, offset DAY_PER_MONTH_L

add_year:
	cmp	ebx, ecx
	jb	store_year

	sub	ebx, ecx
	inc	ax
	jmp	next_year

store_year:
	push	ax			; store year

	; calculate month
	xor	eax, eax
	
next_month:
	; secs per month
	push	eax			; store current month
	mov	di, ax
	mov	al, [di + bp]		; days per month
	mov	ecx, 24 * 60 * 60	; secs per day
	mul	ecx			; eax = eax * ecx
	mov	ecx, eax		; ecx = secs per month
	pop	eax			; restore current month

	cmp	ebx, ecx
	jb	store_month

	sub	ebx, ecx
	inc	al
	jmp	next_month

store_month:
	inc	al			; month starts from 1
	push	ax			; store month

	; calculate day
	mov	eax, ebx
	xor	edx, edx
	mov	ecx, 24 * 60 * 60
	div	ecx		; EAX:=EDX:EAX / sec per day EDX:=Rest

	inc	al			; day starts from 1
	push	ax			; store day

	; calculate hour
	mov	eax, edx
	xor	edx, edx
	mov	ecx, 60 * 60
	div	ecx

	push	ax

	; calculate minute
	mov	ax, dx
	xor	dx, dx
	mov	cx, 60
	div	cx

	; now al holds minute, dl holds second
conv_time	endp

;
; set system time & print new time
;
	mov	bx, offset MSG_CURRENT

	; time
	mov	cl, al			; minute for DOS call
	mov	di, 14			; msg minute offset
	call	val_to_str

	mov	ax, dx			; second
	shl	dx, 8			; for DOS call
	mov	di, 17			; msg second offset
	call	val_to_str

	pop	ax			; hour
	mov	ch, al			; for DOS call
	mov	di, 11			; msg hour offset
	call	val_to_str

	mov	ah, 2dh			; set system time funciton
	int	21h

	; date
	pop	ax			; day
	mov	dl, al			; for DOS call
	mov	di, 8			; msg day offset
	call	val_to_str

	pop	ax			; month
	mov	dh, al			; for DOS call
	mov	di, 5			; msg month offset
	call	val_to_str

	pop	ax			; year
	mov	cx, ax			; for DOS call
	sub	ax, 2000
	mov	di, 2			; msg year offset
	call	val_to_str

	mov	ah, 2bh			; set system date function
	int	21h

	; print updated time and exit
	mov	dx, bx
	mov	al, 1
	jmp	print_msg

invalid_time:
	mov	dx, offset MSG_TIMENG		; get time NG message
	xor	al, al
	jmp	print_msg

not_in_vm:
	mov	dx, offset MSG_VMNG		; not in VM message
	xor	al, al

print_msg:
	push	ax

	mov 	ah, 09h				; print function 
	int	21h				; DOS function call

	mov	dx, offset END_OF_LINE
	int	21h
	
	pop	ax

terminate:
	mov	ah, 4ch
	int	21h
	; this function never returnes

val_to_str	proc near
	push	cx
	mov	cl, 10
	div	cl
	pop	cx

	add	al, 30h
	add	ah, 30h
	mov	[bx + di], ax
	ret
val_to_str	endp
codes	ends
end	entry
